Skip to content

Log all Fiber errors w/ component stack#8756

Merged
bvaughn merged 7 commits intofacebook:masterfrom
bvaughn:fb-internal-error-dialog
Jan 13, 2017
Merged

Log all Fiber errors w/ component stack#8756
bvaughn merged 7 commits intofacebook:masterfrom
bvaughn:fb-internal-error-dialog

Conversation

@bvaughn
Copy link
Copy Markdown
Contributor

@bvaughn bvaughn commented Jan 12, 2017

A new module has been added (ReactFiberErrorLogger). This logs error information (call stack and component stack) to the console to make errors easier to debug. It also prompts users to use error boundaries if they are not already using them.

In the future, perhaps this will be injectable, enabling users to provide their own handler for custom processing/logging. For the time being, this should help with issues like this / #2461.

A new module has been added (ReactFiberErrorLogger). By default this module just logs error information (call stack and component stack) to the console to make errors easier to debug. In the future, perhaps we will enable users to inject their own handler for custom processing/logging.
function logCapturedError(capturedError : CapturedError) : void {
if (__DEV__) {
// console.log rather than console.error to avoid breaking tests
// (Jest complains about unexpected console.error calls.)
Copy link
Copy Markdown
Collaborator

@gaearon gaearon Jan 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it actually be useful to break tests? e.g. product tests.
We can always spyOn(console, 'error') in error boundary tests.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, though we don't fail tests on console.error at FB, just in this repo.

Copy link
Copy Markdown
Contributor Author

@bvaughn bvaughn Jan 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that tests (at least ones I spot-checked) are already asserting that errors occurred (eg spying on console.error and asserting a specific number of calls). This increases the number though and so the tests failed.

I wanted to limit the size of this commit. But I'd be happy to update the 20-30 tests if you guys have a strong preference. 😄

Edit: I'll swap the 2 console.log calls with a single console.error call and update the tests accordingly.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer if we update the tests to assert on the logs, thanks for checking.

Copy link
Copy Markdown
Contributor Author

@bvaughn bvaughn Jan 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is proving to be a little complicated since Stack doesn't behave the same as Fiber in terms of error-handling behavior. It isn't a matter of just adding console.error calls to Stack.

Copy link
Copy Markdown
Contributor Author

@bvaughn bvaughn Jan 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think an appropriate solution for now is to globally mock ReactFiberErrorLogger (so that Stack and Fiber maintain parity regarding console.error calls). This prevents our tests from forking all over the place.

expect(() => ReactNoop.flush()).toThrow('Error!');
});

if (ReactDOMFeatureFlags.useFiber) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReactNoop used in these tests is already Fiber-specific so this shouldn’t be necessary.

@sophiebits
Copy link
Copy Markdown
Collaborator

Can you show a screenshot of the console for this?

try {
logCapturedError(capturedError);
} catch (e) {
// Prevent cycle if logCapturedError() throws.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we console.error once here too?

@bvaughn
Copy link
Copy Markdown
Contributor Author

bvaughn commented Jan 12, 2017

Can you show a screenshot of the console for this?

screen shot 2017-01-12 at 10 19 57 am

The left triangle can be clicked to expand the call stack.

Suggestions for improved wording welcome! I left a TODO for linking to unstable_handleError documentation once it exists.

@sophiebits
Copy link
Copy Markdown
Collaborator

sophiebits commented Jan 12, 2017

The flippy triangle shows you the console.error stack, not the caught error stack right? If so, let's just include the stack as text.

For wording, what do you think of:

(componentName ? `React caught an error thrown by ${componentName}. ` : `React caught an error thrown by one of your components. `) +
'You should fix this error in your code. ' +
(
  isUsingErrorBoundary ?
  `This error will be handled by the error boundary ${errorBoundaryName}.` :
  (willRetry ? `React will try to recreate this component tree from scratch. ` : `Recreating the tree from scratch failed, so React will unmount the tree. `) +
  `Consider adding an error boundary to your tree to customize error handling behavior.`
)

@sophiebits
Copy link
Copy Markdown
Collaborator

I am worried that the component stack and display names will be useless for in prod mode with minified code. Maybe omit the stack and names in prod? Or I guess we could have some heuristic to find minified identifiers (/[a-z0-9]{1,2}/i maybe) and ignore them.

@bvaughn
Copy link
Copy Markdown
Contributor Author

bvaughn commented Jan 12, 2017

I am worried that the component stack and display names will be useless for in prod mode with minified code. Maybe omit the stack and names in prod? Or I guess we could have some heuristic to find minified identifiers (/[a-z0-9]{1,2}/i maybe) and ignore them.

This message is currently logged in __DEV__ only.

@bvaughn
Copy link
Copy Markdown
Contributor Author

bvaughn commented Jan 12, 2017

I considered checking if they were already using an error boundary (as you mentioned) but didn't. I wasn't sure if I was overly engineering the message. Since you mentioned it too though, I'll add it.

@sophiebits
Copy link
Copy Markdown
Collaborator

Can we log a stripped-down message in prod too? I'm concerned about the case where people have promises eating their errors and don't even know their components are throwing.

@bvaughn
Copy link
Copy Markdown
Contributor Author

bvaughn commented Jan 12, 2017

Definitely.

@bvaughn
Copy link
Copy Markdown
Contributor Author

bvaughn commented Jan 12, 2017

I love the suggested wording improvements! 😁 Here's an example with a component that defines an error boundary but fails on both attempts:
screen shot 2017-01-12 at 3 07 34 pm

And here's one with no error boundary:
screen shot 2017-01-12 at 3 08 40 pm

In production the following is all that we log:
screen shot 2017-01-12 at 3 09 37 pm

@bvaughn bvaughn force-pushed the fb-internal-error-dialog branch from c6b46d8 to 88d2d5c Compare January 12, 2017 23:46
describe('ReactFiberErrorLogger', () => {
function initReactFiberErrorLoggerMock(mock) {
jest.resetModules();
if (mock) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awkward. I'm not sure how to test both unmocked and mocked without explicitly doing this.

@bvaughn
Copy link
Copy Markdown
Contributor Author

bvaughn commented Jan 13, 2017

(Back to you @spicyj)

Copy link
Copy Markdown
Collaborator

@sophiebits sophiebits left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks great to me!

: 'React caught an error thrown by one of your components.';

let errorBoundaryMessage;
if (errorBoundaryFound) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(errorBoundaryFound && errorBoundaryName might be a little cleaner if you like)

@bvaughn bvaughn merged commit be0de34 into facebook:master Jan 13, 2017
@bvaughn bvaughn deleted the fb-internal-error-dialog branch January 13, 2017 22:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants